La derivación automática es la pieza clave del proceso de aprendizaje de cualquier red neuronal. Con PyTorch podemos construir cualquier función (modelo) que necesitemos basada en operaciones sobre tensores. Además del modelo añadiremos una función de pérdida por la que podremos descender por el gradiente con el fin de aproximarnos a algún mínimo. PyTorch nos ofrece la posibilidad de generar automáticamente estos gradientes (vectores de descenso).
https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html
Vamos a derivar una función sencilla como:
y=x2+3x3Sabemos que su derivada es:
y′=2x+9x2Si queremos saber cuál es el valor de la derivada en x=2, podemos calcularlo. Su valor es y′(2)=40. Vamos a implementarlo en PyTorch:
import torch
import numpy as np
x = torch.from_numpy(np.array([2.])) # Creamos un tensor con un solo valor (2.0)
x.requires_grad_(True) # Especificamos que 'x' es una variable sobre la que queremos derivar
y = x**2 + 3*x**3 # Creamos la expresión
y.backward() # Solicitamos a PyTorch que derive
print(x.grad) # Imprimimos la derivada o gradiente sobre 'x'
tensor([40.], dtype=torch.float64)
¿Qué pasa si tenemos una función de más de una variable? Como por ejemplo:
z=4x2+2y3+yOcurre que pasamos de derivar a derivar parcialmente. Esto significa que debemos derivar primero con respecto a x y luego con respecto a y.
∂z∂x=8xdevice = 'cuda' if torch.cuda.is_available() else 'cpu'
x = torch.from_numpy(np.array([2])).float().to(device)
y = torch.from_numpy(np.array([3])).float().to(device)
x.requires_grad_(True)
y.requires_grad_(True)
z = 4*x**2 + 2*y**3 + y
z.backward()
print(x.grad)
print(y.grad)
tensor([16.]) tensor([55.])
Vamos un paso más allá y veamos cómo podríamos tener un número arbitrario de variables a derivar y cómo lo podríamos representar de manera matricial.
device = 'cuda' if torch.cuda.is_available() else 'cpu'
X = torch.from_numpy(np.array([[2,3],[4,5]])).float().to(device)
X.requires_grad_(True)
Y = torch.sum(X**2) # La potenciación actúa sobre cada elemento de la matriz
Y.backward()
print(X.grad)
tensor([[ 4., 6.], [ 8., 10.]])
Lo anterior expresa que tenemos una matriz de variables de 2×2, elevamos cada celda al cuadrado y sumamos todo. Escrito de otra forma: X=(abcd)
import os
os.environ['PATH'] += os.pathsep + "C:\\Program Files\\Graphviz 2.44.1\\bin"
import torch
import torchviz
import numpy as np
x = torch.from_numpy(np.array([2.])) # Creamos un tensor con un solo valor (2.0)
x.requires_grad_(True) # Especificamos que 'x' es una variable sobre la que queremos derivar
y = x**2 + 3*x**3 # Creamos la expresión
y.backward() # Solicitamos a PyTorch que derive
print(x.grad) # Imprimimos la derivada o gradiente sobre 'x'
torchviz.make_dot(y)
tensor([40.], dtype=torch.float64)
Veamos qué significa esto:
x = torch.from_numpy(np.array([2.]))
y = torch.from_numpy(np.array([3.]))
x.requires_grad_(True)
y.requires_grad_(True)
z = (4*x**2 + 2*y**3 + y)
z.backward()
torchviz.make_dot(z)
X = torch.from_numpy(np.array([[2,3],[4,5],[6,7]])).float()
X.requires_grad_(True)
Y = torch.sum(X**2) # La potenciación actúa sobre cada elemento de la matriz
Y.backward()
print(X.grad)
torchviz.make_dot(Y)
tensor([[ 4., 6.], [ 8., 10.], [12., 14.]])